/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.panomedic.core;

import com.panomedic.Constants;
import com.panomedic.colors.ColorConstants;
import com.panomedic.log4j.LogCategories;
import com.panomedic.log4j.Logger;
import com.panomedic.utils.Utils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;

/**
 *
 * @author Yare
 */
public class Intersection implements Comparable {

    static Logger logger = new Logger();
    /** intersection position */
    protected Point topLeft,  bottomRight;
    /** Intersection size */
    Dimension intSize = null;
    /** number of opaque pixels in intersected photos (all of them must be opaque) */
    protected int size;
    /** photos involved in intersection */
    protected HashSet<Photo> involved = new HashSet();
    protected HashSet<Histogram> histograms = new HashSet();
    /** histograms with logs */
    protected LogHistogram[] logHist = new LogHistogram[2];
    /** opaque intersection */
    protected Point opaqueTL = new Point(-1, -1);
    protected Point opaqueBR = new Point(-1, -1);
    /** cutting boundary HashSet*/
    protected HashSet<Point> cutBoundary = null;

    /**
     * States of found pixels in involved photos 
     * NONE: none of pixels is opaque
     * SOME: one or more are opqaue
     * ALL: all of the pixels are opaque
     */
    protected enum isPxOpaqueEnum {

        NONE, SOME, ALL
    };
    /** array field of the the same size as intersection - stores values of number photos with opaque pixels */
    protected isPxOpaqueEnum[][] bothOpaque = null;
    /** boundary end points */
    protected ArrayList<Point> boundaryEnds = null;

    boolean isBoundaryEnd(Point point) {
        return boundaryEnds.contains(point);
    }
    protected Photo pActive = null;
    protected Preferences prefs;
    int csType = ColorConstants.HSV;

    public Intersection(Photo photo1, Photo photo2) {
        this.addPhoto(photo1);
        this.addPhoto(photo2);

        prefs = Preferences.userRoot().node(Constants.defNodePath);
        csType = prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV);


        Point tl = photo1.getInterTopLeft(photo2);
        Point br = photo1.getInterBottomRight(photo2);
        this.setTopLeft(tl);
        this.setBottomRight(br);
        this.setSize();

        intSize = new Dimension(br.x - tl.x + 1, br.y - tl.y + 1);


        logger.debug(LogCategories.PROCESSING, "Intersection created: TL = " + topLeft + ", BR = " + bottomRight);
        logger.debug(LogCategories.PROCESSING, "Dimension: " + intSize);
    }

    public void findBoundary() {
        // debug
        if (findOpaqueSubimageBoundaries()) {
            findBoundaryEnds();

            //  choosing the image to find the boundary from
            Photo p1 = getFirst();
            Photo p2 = getSecond();

            //  choose the image with less extreme values - extreme threshold
            double threshold = 0.02;

            Photo pFrom = (p1.getExtremeCount(topLeft, bottomRight, threshold) < p2.getExtremeCount(topLeft, bottomRight, threshold) ? p1 : p2);

            BoundarySeeker bs = new BoundarySeeker(pFrom.getImage().getSubimage(topLeft.x, topLeft.y, intSize.width, intSize.height),
                    bothOpaque, boundaryEnds, topLeft);
            bs.initialize();
            cutBoundary = bs.seek(boundaryEnds);
        }
    }

    public boolean inCutBoundary(Point point) {
        return cutBoundary.contains(point);
    }

    public void addPhoto(Photo photo) {
        involved.add(photo);
    }

    public boolean contains(Photo photo) {
        return involved.contains(photo);
    }

    public boolean contains(int x, int y) {
        return (topLeft.x <= x && topLeft.y <= y &&
                bottomRight.x >= x && bottomRight.y >= y);
    }

    /**
     * returns the other photo from the intersection then the parameter
     * @param photo
     * @return
     */
    public Photo neighbour(Photo photo) {
        Iterator it = involved.iterator();
        if (it.hasNext()) {
            Photo p1 = (Photo) it.next();
            if (photo == p1) {
                return (Photo) it.next();
            } else {
                return p1;
            }
        }
        return null;
    }

    public Photo getFirst() {
        Iterator it = involved.iterator();
        if (it.hasNext()) {
            return (Photo) it.next();
        }
        return null;
    }

    public Photo getSecond() {
        Iterator it = involved.iterator();
        if (it.hasNext()) {
            it.next();
        }
        if (it.hasNext()) {
            return (Photo) it.next();
        }
        return null;
    }

    // temp functions
    public Histogram getFirstHist() {
        Iterator ith = histograms.iterator();
        Iterator itp = involved.iterator();
        if (ith.hasNext() && itp.hasNext()) {
            Histogram h1 = (Histogram) ith.next();
            if (h1.getPhoto() == (Photo) itp.next()) {
                return h1;
            } else if (ith.hasNext()) {
                return ((Histogram) ith.next());
            }
        }
        return null;
    }

    public Histogram getSecondHist() {
        Iterator ith = histograms.iterator();
        Iterator itp = involved.iterator();
        if (ith.hasNext() && itp.hasNext()) {
            Histogram h1 = (Histogram) ith.next();
            if (h1.getPhoto() == (Photo) itp.next()) {
                if (ith.hasNext()) {
                    return ((Histogram) ith.next());
                }
            } else {
                return h1;
            }
        }
        return null;
    }

    /**
     *  Return histogram of given photo if present in involved
     * @param photo
     * @return
     */
    public Histogram getHist(Photo photo) {
        Iterator ith = histograms.iterator();
        while (ith.hasNext()) {
            Histogram h = (Histogram) ith.next();
            if (h.getPhoto() == photo) {
                return h;
            }
        }
        return null;
    }

    public void determineTransFnc() {
        Histogram hist;
        int ip = 0;
        for (Photo photo : involved) {
            hist = new Histogram(photo, topLeft, bottomRight);
            histograms.add(hist);
            logHist[ip] = new LogHistogram(photo, topLeft, bottomRight);
            ip++;
        }


        double[] gamma = new double[]{1., 1., 1.};
//        double[] gamma = countGamma();
        getFirstHist().count(gamma);
        getSecondHist().count(gamma);

        for (int j = 0; j < involved.size(); j++) {
            logHist[j].count();
        }


        double[] m1 = logHist[0].getMean();
        double[] m2 = logHist[1].getMean();
        double[] s1 = logHist[0].getSD();
        double[] s2 = logHist[1].getSD();

        double[] exp1 = new double[3];
        double[] exp2 = new double[3];
        double[] shift1 = new double[3];
        double[] shift2 = new double[3];



        for (int i = 0; i < 3; i++) {
            double exp = 1;
            if (s2[i] != 0) {
                exp = s1[i] / s2[i];
            }
            exp1[i] = 1.0;
            exp2[i] = exp;

            double shift = (m1[i] - exp * m2[i]);
            shift1[i] = 1;
            shift2[i] = Math.exp(shift);

        }
        
        logHist[0].getPhoto().setBLog(shift1);
        logHist[1].getPhoto().setBLog(shift2);
        logHist[0].getPhoto().setCLog(exp1);
        logHist[1].getPhoto().setCLog(exp2);
        logHist[0].getPhoto().setShifted(true);
        logHist[1].getPhoto().setShifted(true);
        
        

        Photo pX = logHist[1].getPhoto();
        double[] power = exp2;
        double[] mul = shift2;

        logger.debug(LogCategories.PROCESSING, "b = " + Arrays.toString(shift2) + ", c = " + Arrays.toString(exp2));
//        WritableRaster wr = pX.getRaster().createCompatibleWritableRaster(topLeft.x, topLeft.y, intSize.width, intSize.height);

        myRaster wr = new myRaster(intSize.width, intSize.height);
//
//        for (int i = 0; i < mul.length; i++) {
//            mul[i] = Math.pow(Math.E, mul[i]);
//
//        }

        for (int y = topLeft.y; y <= bottomRight.y; y++) {
            for (int x = topLeft.x; x <= bottomRight.x; x++) {

                int[] px = null;
                double[] px2 = new double[4];

                px = pX.getRaster().getPixel(x, y, px);
                if (Utils.isPixelOpaque(px)) {
                    double[] array = Utils.getPixelValues(px, csType);
                    for (int i = 0; i < 3; i++) {
                        px2[i] = mul[i] * Math.pow(array[i], power[i]);
                    }
                    px2[3] = 255;
                    try {
                        wr.setPixel(x - topLeft.x, y - topLeft.y, px2);
                    } catch (Exception e) {
                        System.err.println("ERROR ON SETPIXEL: " + e);
                    }
                }
            }
        }

        wr.count();
        logger.debug(LogCategories.PROCESSING, "SHIFTED LOGS : ");
        logger.debug(LogCategories.PROCESSING, logHist[0].getPhoto() + " :: " + Arrays.toString(shift1) + ", " + Arrays.toString(exp1));
        logger.debug(LogCategories.PROCESSING, logHist[1].getPhoto() + " :: " + Arrays.toString(shift2) + ", " + Arrays.toString(exp2));
//        logger.debug(LogCategories.PROCESSING, "X' :: " + Arrays.toString(wr.getMean()) + ", " + Arrays.toString(wr.getSD()));

        double[] mulInit = new double[]{1., 1., 1.};
        double[] powerInit = new double[]{1., 1., 1.};

//        logHist[0].getPhoto().setCLog(powerInit);
//        logHist[1].getPhoto().setCLog(power);
//        logHist[0].getPhoto().setBLog(mulInit);
//        logHist[1].getPhoto().setBLog(mul);

        double[] mean1 = getHist(logHist[0].getPhoto()).getMean();

        shift1 = new double[3];
        shift2 = new double[3];

        for (int i = 0; i < mean1.length; i++) {

            if (mean1[i] > wr.getMean()[i]) {
                shift1[i] = 0;
                shift2[i] = mean1[i] - wr.getMean()[i];
            } else {
                shift1[i] = wr.getMean()[i] - mean1[i];
                shift2[i] = 0;
            }

        }
        logHist[0].getPhoto().setShift(shift1);
        logHist[1].getPhoto().setShift(shift2);




    }

    private double[] countGamma() {
        Histogram h1 = getFirstHist();
        Histogram h2 = getSecondHist();
        double[] sum = h1.countGamma(h2);
        return sum;

    }

    /**
     * Takes two first histograms (of intersected parts of first two photos involved in this intersection)
     * and counts shifts for the their origin photos as difference of means
     */
    public void setPhotosShifts2() {
        Iterator it = histograms.iterator();
        Histogram first;
        Histogram second;

        logger.info(LogCategories.PROCESSING, "Setting shifts...");
        if (it.hasNext()) {
            first = (Histogram) it.next();
            if (it.hasNext()) {
                second = (Histogram) it.next();


                logger.info(LogCategories.PROCESSING, first.getPhoto() + " :: " + Arrays.toString(first.getMean()) + ", " + Arrays.toString(first.getSD()));
                logger.info(LogCategories.PROCESSING, second.getPhoto() + " :: " + Arrays.toString(second.getMean()) + ", " + Arrays.toString(second.getSD()));

                //TODO make it floodfill

                if (first.getPhoto().isShifted() && second.getPhoto().isShifted()) {

                    logger.debug(LogCategories.PROCESSING, "Photos already shifted.");
                    return;
                }
                if (first.getPhoto().isShifted() && !second.getPhoto().isShifted()) {
                    double[] shift = new double[]{0.0, 0.0, 0.0};
                    double[] expand = new double[]{1.0, 1.0, 1.0};

                    for (int i = 0; i < 3; i++) {
                        shift[i] = second.getMean()[i] - first.getMean()[i] + first.getPhoto().getShift()[i];
                        double tmp = first.getSD()[i] * first.getPhoto().getExpand()[i];
                        if (tmp != 0) {
                            expand[i] = second.getSD()[i] / tmp;
                        } else {
                            expand[i] = 1;
                        }
                    }
                    second.getPhoto().setShift(shift);
                    second.getPhoto().setExpand(expand);
                    second.getPhoto().setShifted(true);

                    logger.debug(LogCategories.PROCESSING, "One photo shifted.");
                    return;
                }
                if (!first.getPhoto().isShifted() && second.getPhoto().isShifted()) {
                    double[] shift = new double[]{0.0, 0.0, 0.0};
                    double[] expand = new double[]{1.0, 1.0, 1.0};

                    for (int i = 0; i < 3; i++) {
                        shift[i] = second.getPhoto().getShift()[i] -
                                (second.getMean()[i] - first.getMean()[i]);
                        double tmp = (second.getSD()[i] / first.getSD()[i]);
                        if (tmp != 0) {
                            expand[i] = second.getPhoto().getExpand()[i] / tmp;
                        } else {
                            expand[i] = 1;
                        }
                    }
                    first.getPhoto().setShift(shift);
                    first.getPhoto().setExpand(expand);
                    first.getPhoto().setShifted(true);

                    logger.debug(LogCategories.PROCESSING, "One photo shifted.");
                    return;
                }
                if (!first.getPhoto().isShifted() && !second.getPhoto().isShifted()) {

//                    first.getPhoto().setShift(first.getMean());
//                    second.getPhoto().setShift(second.getMean());

                    double[] m1 = first.getMean();
                    double[] m2 = second.getMean();
                    double[] s1 = first.getSD();
                    double[] s2 = second.getSD();

                    double[] exp1 = new double[3];
                    double[] exp2 = new double[3];
                    double[] shift1 = new double[3];
                    double[] shift2 = new double[3];

                    for (int i = 0; i < 3; i++) {
                        if (s1[i] < s2[i]) {
                            double exp = 1;
                            if (s1[i] != 0) {
                                exp = s2[i] / s1[i];
                            }
                            exp1[i] = exp;
                            exp2[i] = 1.0;
                            double shift = (exp * m1[i] - m2[i]);
                            if (exp * m1[i] > m2[i]) {
                                shift1[i] = 0;
                                shift2[i] = shift;
                            } else {
                                shift1[i] = -shift;
                                shift2[i] = 0;
                            }
                        } else {
                            double exp = 1;
                            if (s2[i] != 0) {
                                exp = s1[i] / s2[i];
                            }
                            exp1[i] = 1.0;
                            exp2[i] = exp;
                            double shift = (exp * m2[i] - m1[i]);
                            if (exp * m2[i] > m1[i]) {
                                shift1[i] = shift;
                                shift2[i] = 0;
                            } else {
                                shift1[i] = 0;
                                shift2[i] = -shift;
                            }
                        }
                    }
                    first.getPhoto().setExpand(exp1);
                    second.getPhoto().setExpand(exp2);
                    first.getPhoto().setShift(shift1);
                    second.getPhoto().setShift(shift2);





//                    first.getPhoto().setShift(0);
//                    first.getPhoto().setExpand(1.0);
//                    double exp = first.getSD()/second.getSD();
//                    second.getPhoto().setExpand(exp);
//                    second.getPhoto().setShift(first.getMean()-second.getMean()*exp);

                    first.getPhoto().setShifted(true);
                    second.getPhoto().setShifted(true);
                    logger.debug(LogCategories.PROCESSING, "Both photos shifted.");
                    return;

                }
            }
        }
        logger.debug(LogCategories.PROCESSING, "Photos have not been shifted.");
    }

    @Override
    public String toString() {
        String str;
        str = "Intersection :: topLeft = " + topLeft +
                ",bottomRight=" + bottomRight + ",size=" + size +
                involved + "\n";
        return str;
    }

    public Point getBottomRight() {
        return bottomRight;
    }

    public void setBottomRight(Point bottomRight) {
        this.bottomRight = bottomRight;
    }

    public Point getTopLeft() {
        return topLeft;
    }

    public void setTopLeft(Point topLeft) {
        this.topLeft = topLeft;
    }

    public int compareTo(Object o) throws ClassCastException {
        if (!(o instanceof Intersection)) {
            throw new ClassCastException("An Intersection object expected.");
        }
        int size2 = ((Intersection) o).getSize();
        return size2 - this.getSize();
    }

    public int getSize() {
        return size;
    }

    public void setSize() {
        size = 0;
        for (int x = topLeft.x; x < bottomRight.x; x++) {
            for (int y = topLeft.y; y < bottomRight.y; y++) {
                boolean count = true;
                for (Iterator it = involved.iterator(); it.hasNext();) {
                    Photo photo = (Photo) it.next();
                    int px[] = null;
                    px = photo.getRaster().getPixel(x, y, px);
                    if (!Utils.isPixelOpaque(px)) {
                        count = false;
                    }
                }
                if (count) {
                    size++;
                }
            }
        }
    }

    /**
     * Finds the opaque part of intersection -> opaqueTL, opaqueBR
     * Fills the bothOpaque array (both two involved photos have the pixel opaque)
     * ! implemented for more than 2 involved photos
     */
    public boolean findOpaqueSubimageBoundaries() {
        int width = bottomRight.x - topLeft.x + 1;
        int height = bottomRight.y - topLeft.y + 1;
        opaqueTL.x = bottomRight.x + 1;
        opaqueTL.y = bottomRight.y + 1;

        int involvedCount = involved.size();

//        isPxOpaqueEnum[][] bothOpaqueIntersection = new isPxOpaqueEnum[width][height];
        bothOpaque = new isPxOpaqueEnum[width][height];

        for (int y = topLeft.y; y <= bottomRight.y; y++) {
            for (int x = topLeft.x; x <= bottomRight.x; x++) {
                switch (isPixelOpaque(x, y)) {
                    case ALL:
                        bothOpaque[x - topLeft.x][y - topLeft.y] = isPxOpaqueEnum.ALL;
                        if (x < opaqueTL.x) {
                            opaqueTL.x = x;
                        }
                        if (y < opaqueTL.y) {
                            opaqueTL.y = y;
                        }
                        if (x > opaqueBR.x) {
                            opaqueBR.x = x;
                        }
                        if (y > opaqueBR.y) {
                            opaqueBR.y = y;
                        }
                        break;
                    case SOME:
                        bothOpaque[x - topLeft.x][y - topLeft.y] = isPxOpaqueEnum.SOME;
                        break;
                    case NONE:
                        bothOpaque[x - topLeft.x][y - topLeft.y] = isPxOpaqueEnum.NONE;
                        break;
                }


            }
        }

        logger.debug(LogCategories.PROCESSING, "opaqueTL = " + opaqueTL);
        logger.debug(LogCategories.PROCESSING, "opaqueBR = " + opaqueBR);

        if (opaqueTL.x == -1) {
            //  no opaque points in intersection
            return false;
        } else {
            return true;
        }

    }

    /**
     * Finds possible boundary ends in given intersection.
     * Point can be end if it's opaque in all involved one of his neighbours is not 
     * @return true if the searching goes successfully
     */
    public boolean findBoundaryEnds() {

        logger.debug(LogCategories.PROCESSING, "Finding possible boundary ends...");

        if (bothOpaque == null) {
            return false;
        }
        int width = bottomRight.x - topLeft.x + 1;
        int height = bottomRight.y - topLeft.y + 1;


        /**
         * Create new array +2 on both sides
         */
        isPxOpaqueEnum[][] transBitMapEdged = new isPxOpaqueEnum[width + 2][height + 2];

        //  Fill it with the old to the center, leaving the side columns/rows null
        for (int i = 1; i < transBitMapEdged.length - 1; ++i) {
            System.arraycopy(bothOpaque[i - 1], 0, transBitMapEdged[i], 1, height);
        }

        //  fill the sides
        for (int i = 0; i < transBitMapEdged.length - 2; ++i) {
            Point p1 = new Point(topLeft.x + i, topLeft.y - 1);
            Point p2 = new Point(topLeft.x + i, bottomRight.y + 1);

            transBitMapEdged[i + 1][0] = isPixelOpaque(p1.x, p1.y);
            transBitMapEdged[i + 1][height + 1] = isPixelOpaque(p2.x, p2.y);
        }
//        for (int j = 0; j < transBitMapEdged[0].length-2; j++) {
//            Point p1 = new Point(topLeft.x - 1, topLeft.y + j);
//            Point p2 = new Point(bottomRight.x + 1, topLeft.y + j);
//            
//            transBitMapEdged[0][j+1] = isPixelOpaque(p1.x, p2.x);
//            transBitMapEdged[width+2][j+1] = isPixelOpaque(p1.x, p2.x);
//        }
        //  variant with corners
        for (int j = 0; j < transBitMapEdged[0].length; j++) {
            Point p1 = new Point(topLeft.x - 1, topLeft.y + j - 1);
            Point p2 = new Point(bottomRight.x + 1, topLeft.y + j - 1);

            transBitMapEdged[0][j] = isPixelOpaque(p1.x, p1.y);
            transBitMapEdged[width + 1][j] = isPixelOpaque(p2.x, p2.y);
        }



        boundaryEnds = new ArrayList();

        for (int y = 1; y < transBitMapEdged[0].length - 1; y++) {
            for (int x = 1; x < transBitMapEdged.length - 1; x++) {

                // DEBUG 
                if (x == 374 - topLeft.x + 1 && y == 171 - topLeft.y + 1) {
                    int dbg = 0;
                }
                // ~DEBUG

                boolean isEndPoint = false;
                switch (transBitMapEdged[x][y]) {
                    case ALL:
                        if (countAsPossibleEnd(transBitMapEdged, x, y - 1) || countAsPossibleEnd(transBitMapEdged, x, y + 1) || countAsPossibleEnd(transBitMapEdged, x + 1, y - 1) || countAsPossibleEnd(transBitMapEdged, x + 1, y) || countAsPossibleEnd(transBitMapEdged, x + 1, y + 1) || countAsPossibleEnd(transBitMapEdged, x - 1, y - 1) || countAsPossibleEnd(transBitMapEdged, x - 1, y) || countAsPossibleEnd(transBitMapEdged, x - 1, y + 1)) {
                            isEndPoint = true;
                        }
                        if (isEndPoint) {
                            boundaryEnds.add(new Point(x - 1 + topLeft.x, y - 1 + topLeft.y));
                        }
                        break;
                }
            }

        }

//        logger.debug(LogCategories.PROCESSING, "Found boundary points count: " + boundaryEnds.size());
//        logger.debug(LogCategories.PROCESSING, "Points: " + boundaryEnds.toString());

        boundaryEnds = ConnectedComponent.filter(boundaryEnds);

        logger.debug(LogCategories.PROCESSING, "Found boundary points count: " + boundaryEnds.size());
        logger.debug(LogCategories.PROCESSING, "Points: " + boundaryEnds.toString());

        // DEBUG
//        drawEnds();
//        drawTransparency(transBitMapEdged);

        return true;
    }

    /**
     * Supporting function for void findBoundaryEnds()
     * @param bm
     * @param x
     * @param y
     * @return
     */
    protected boolean countAsPossibleEnd(isPxOpaqueEnum[][] bm, int x, int y) {
        if (bm[x][y] == isPxOpaqueEnum.NONE || bm[x][y] == null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * debug function to draw all the points into image and saves it
     */
    protected void drawEnds() {

        BufferedImage bi = involved.iterator().next().getImage();
        WritableRaster wr = bi.getRaster();
        ListIterator li = boundaryEnds.listIterator();
        float[] px = new float[3];
        Color color = Color.YELLOW;
        px = color.getRGBColorComponents(px);
        int[] px2 = new int[4];
        px2[0] = (int) (255 * px[0]);
        px2[1] = (int) (255 * px[1]);
        px2[2] = (int) (255 * px[2]);
        px2[3] = 255;

        while (li.hasNext()) {
            Point p = (Point) li.next();

            try {
                wr.setPixel(p.x, p.y, px2);
            } catch (ArrayIndexOutOfBoundsException ex) {
                logger.error(LogCategories.PROCESSING, "setPixel - arrayIndexOut: p.x = " + p.x + ", p.y = " + p.y + "\t Ex:" + ex.toString());
            }
        }
        bi.setData(wr);


        File file = new File("points.png");
        try {
            ImageIO.write(bi, "png", file);
        } catch (IOException ex) {
            logger.debug(ex.getMessage());
        }


    }

    /**
     * debug function to draw all the points into image and saves it
     */
    protected void drawTransparency(isPxOpaqueEnum[][] transBitMapEdged) {

//        BufferedImage bi = involved.iterator().next().getImage();
        BufferedImage bi = new BufferedImage(transBitMapEdged.length, transBitMapEdged[0].length, BufferedImage.TYPE_INT_ARGB);

        WritableRaster wr = bi.getRaster();

        // px1 = NONE
        float[] px = new float[3];
        Color color = Color.MAGENTA;
        px = color.getRGBColorComponents(px);
        int[] px1 = new int[4];
        px1[0] = (int) (255 * px[0]);
        px1[1] = (int) (255 * px[1]);
        px1[2] = (int) (255 * px[2]);
        px1[3] = 255;

        // px2 = SOME
        color = Color.CYAN;
        px = color.getRGBColorComponents(px);
        int[] px2 = new int[4];
        px2[0] = (int) (255 * px[0]);
        px2[1] = (int) (255 * px[1]);
        px2[2] = (int) (255 * px[2]);
        px2[3] = 255;

        // px3 = ALL
        color = Color.DARK_GRAY;
        px = color.getRGBColorComponents(px);
        int[] px3 = new int[4];
        px3[0] = (int) (255 * px[0]);
        px3[1] = (int) (255 * px[1]);
        px3[2] = (int) (255 * px[2]);
        px3[3] = 255;

        // px4 = out of image
        color = Color.BLACK;
        px = color.getRGBColorComponents(px);
        int[] px4 = new int[4];
        px4[0] = (int) (255 * px[0]);
        px4[1] = (int) (255 * px[1]);
        px4[2] = (int) (255 * px[2]);
        px4[3] = 255;



        logger.debug(LogCategories.PREPROCESS, "transBitMapEdged - dimensions: " + transBitMapEdged.length +
                "," + transBitMapEdged[0].length);


        for (int y = 0; y < transBitMapEdged[0].length; y++) {
            for (int x = 0; x < transBitMapEdged.length; x++) {
                Point p = new Point(x, y);


                try {
                    if (transBitMapEdged[x][y] == null) {
                        wr.setPixel(p.x, p.y, px4);
                    } else {
                        switch (transBitMapEdged[x][y]) {
                            case NONE:
                                wr.setPixel(p.x, p.y, px1);
                                break;
                            case SOME:
                                wr.setPixel(p.x, p.y, px2);
                                break;
                            case ALL:
                                wr.setPixel(p.x, p.y, px3);
                                break;
                            default:
                                wr.setPixel(p.x, p.y, px4);
                                break;
                        }
                    }
                } catch (ArrayIndexOutOfBoundsException ex) {
                    logger.error(LogCategories.PROCESSING, "setPixel - arrayIndexOut: p.x = " + p.x + ", p.y = " + p.y + "\t Ex:" + ex.toString());
                }
            }
        }
        bi.setData(wr);

        // DEBUG
        File file = new File("transBM.png");
        try {
            ImageIO.write(bi, "png", file);
        } catch (IOException ex) {
            logger.debug(ex.getMessage());
        }


    }

    /**
     * Return value from isPxOpaqueEnum if pixel in the given position is opaque in all involved photos
     * if there is no involved photo or the pixel is out of bounds, returns null
     */
    protected isPxOpaqueEnum isPixelOpaque(
            int x, int y) {

        int involvedCount = involved.size();
        if (size == 0) {
            return null;
        }

        BufferedImage bi = involved.iterator().next().getImage();
        if (x < 0 || y < 0 || x >= bi.getWidth() || y >= bi.getHeight()) {
            return null;
        }

        int opaqueCount = 0;
        for (Iterator it = involved.iterator(); it.hasNext();) {
            Photo photo = (Photo) it.next();
            int px[] = null;
            px =
                    photo.getRaster().getPixel(x, y, px);
            if (Utils.isPixelOpaque(px)) {
                opaqueCount++;
            }

        }
        if (opaqueCount == involvedCount) {
            return isPxOpaqueEnum.ALL;
        } else if (opaqueCount > 0) {
            return isPxOpaqueEnum.SOME;
        } else {
            return isPxOpaqueEnum.NONE;
        }
    }
}

 